/*
 * Root netserver + client library routines
 */ 

#include <signal.h>
#include <errno.h>

#include "ns.h"
#include "eth.h"

#define NS_BUF_LEN 100

int 
netserver_get_packet_socket() 
{
    int    ipc_sock;
    int    packet_sock = -1;
    struct sockaddr_un ipc_sa;
    int    rc;
    struct msghdr rmsg;
    struct cmsghdr *cmsg;
    struct iovec iov[1];
    char   rbuf[NS_BUF_LEN];
    char   cbuf[CMSG_SPACE(NS_BUF_LEN)];
    
    rmsg.msg_name = 0;
    rmsg.msg_namelen = 0;
    rmsg.msg_iov = iov;
    rmsg.msg_iovlen = 1; 
    rmsg.msg_control = cbuf;
    rmsg.msg_controllen = sizeof(cbuf);
 
    iov[0].iov_base = rbuf;
    iov[0].iov_len = NS_BUF_LEN;

    memset(&ipc_sa, 0, sizeof(ipc_sa));
    ipc_sa.sun_family = AF_UNIX;
    strcpy(ipc_sa.sun_path, NETSERVER_PATH);

    ipc_sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (ipc_sock < 0) {
        fprintf(stderr, "ERROR: netserver client ipc socket creation failed\n");
        return -1;
    }

    rc = connect(ipc_sock, (struct sockaddr*) &ipc_sa, sizeof(ipc_sa));
    if (rc != 0) {
        fprintf(stderr, "ERROR: netserver client ipc socket connect failed\n");
        return -1;
    }

    rc = recvmsg(ipc_sock, &rmsg, 0);
    if (rc < 0) { 
        fprintf(stderr, "ERROR: netserver client ipc socket read failed: %d\n", errno);
    } 


    cmsg = CMSG_FIRSTHDR(&rmsg);
    if (!cmsg->cmsg_type == SCM_RIGHTS) {
        fprintf(stderr, "ERROR: netserver client ipc socket read invalid data\n");
    }

    packet_sock = *(int*)CMSG_DATA(cmsg);

    usleep(1000);

    close(ipc_sock);

    return packet_sock;
}



static int 
netserver_send_packet_socket(int client, int packet_sock)
{
    struct  msghdr msg;
    char    ccmsg[NS_BUF_LEN];
    struct  cmsghdr *cmsg;
    struct  iovec vec;
    char   *str = "x";
    int     rc = 0;
    int    *fd;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    vec.iov_base = str;
    vec.iov_len = 1;
    msg.msg_iov = &vec;
    msg.msg_iovlen = 1;

    msg.msg_control = ccmsg;
    msg.msg_controllen = sizeof(ccmsg);

    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(packet_sock));
  
    msg.msg_controllen = cmsg->cmsg_len;
    fd = (void *) (CMSG_DATA(cmsg));
    memcpy(fd, &packet_sock, sizeof(packet_sock));
    msg.msg_flags = 0;

    rc = sendmsg(client, &msg, 0);

    return rc;
}


#ifndef NETSERVER_CLIENT
int main(int argc, char *argv[]) 
{
    int    client, server;
    struct sockaddr_un  client_sa, server_sa;
    int    client_sa_len, server_sa_len;
    char   path[NS_BUF_LEN];
    int    rc;
    int    on;
    int    packet_sock;

    signal(SIGPIPE, SIG_IGN);

    server = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server < 0) {
        fprintf(stderr, "ERROR: netserver ipc socket creation failed\n");
        return -1;
    }   
 
    server_sa.sun_family = AF_UNIX;
    strcpy(server_sa.sun_path, NETSERVER_PATH);
    unlink(server_sa.sun_path);
    server_sa_len = strlen(server_sa.sun_path) + sizeof(server_sa.sun_family);

    rc = bind(server, (struct sockaddr*) &server_sa, server_sa_len);
    if (rc < 0) {
        fprintf(stderr, "ERROR: netserver ipc socket bind failed\n");
        return -1;
    }

    /*
     * Make sure everyone can access this path
     */
    memset(path, 0, NS_BUF_LEN);
    sprintf(path, "chmod 777 %s", NETSERVER_PATH);
    system(path);
 
    /*
     * Allow passing of credentials on the server socket
     */
    rc = setsockopt(server, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
    if (rc < 0) {
        fprintf(stderr, "ERROR: netserver ipc socket setsockopt failed\n");
        return -1;
    }
    
    /*
     * Set up the backlog
     */
    rc = listen(server, 100);
    if (rc < 0) {
        fprintf(stderr, "ERROR: netserver ipc listen failed\n");
        return -1;
    }

    /*
     * Main service loop.  For now, a client connecting to the server implies
     * that the client is requesting a packet socket, so right after accepting
     * a client, we will create a packet socket and send it over.
     */
    while (1) {

        client = accept(server, (struct sockaddr*) &client_sa, &client_sa_len);

        /*
         * Enable credentials for client
         */
        rc = setsockopt(client, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
        if (rc < 0) {
            fprintf(stderr, "ERROR: netserver accepted socket setsockopt failed\n");
            close(client);
            continue;
        }

        /*
         * Allocate a packet socket since we can (we should be root)
         */
        packet_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
        if (packet_sock < 0) {
            fprintf(stderr, "ERROR: netserver packet socket create failed\n");
            close(client);
            continue;
        }
 

        /*
         * Send the privileged socket fd to the client via IPC
         */
        rc = netserver_send_packet_socket(client, socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)));
        if (rc < 0) {
            fprintf(stderr, "ERROR: netserver packet-socket fd send failed\n");
            close(client);
            continue;
        }
    }     

    return 0;
}
#endif

